#include "AbstractSplines.h"
#include "RealSquareMatrices.h"
#include "DCoordinates.h"

using namespace cagd;
using namespace std;

/*--------------------------------------------*
 | Implementation of class SplineInformation. |
 *--------------------------------------------*/

SplineInformation::SplineInformation(GLuint maximum_order_of_derivatives):
        ColumnMatrix<DCoordinate>(maximum_order_of_derivatives + 1)
{
}

GLvoid SplineInformation::LoadNullVectors()
{
    for (GLuint r = 0; r < _row_count; ++r)
    {
        DCoordinate& ref = operator ()(r);
        ref.x = ref.y = ref.z = 0.0;
    }
}

/*----------------------------------------------*
 | Implementation of class ApproximatingSpline. |
 *----------------------------------------------*/

ApproximatingSpline::ApproximatingSpline(GLdouble t_min, GLdouble t_max, GLuint control_point_count, GLboolean is_closed, GLenum usage_flag_control_polygon):
        _is_closed(is_closed),
        _vbo_control_polygon(0),
        _usage_flag_control_polygon(usage_flag_control_polygon),
        _t_min(t_min), _t_max(t_max),
        _control_point(ColumnMatrix<DCoordinate>(control_point_count))
{
}

ApproximatingSpline::ApproximatingSpline(const ApproximatingSpline& spline):
        _is_closed(spline._is_closed),
        _vbo_control_polygon(0),
        _usage_flag_control_polygon(spline._usage_flag_control_polygon),
        _t_min(spline._t_min), _t_max(spline._t_max),
        _control_point(spline._control_point)
{
    if (spline._vbo_control_polygon)
        UpdateVertexBufferObjectOfControlPolygon(_usage_flag_control_polygon);
}

ApproximatingSpline& ApproximatingSpline::operator =(const ApproximatingSpline& rhs)
{
    if (this != &rhs)
    {
        DeleteVertexBufferObjectOfControlPolygon();

        _is_closed = rhs._is_closed;
        _usage_flag_control_polygon = rhs._usage_flag_control_polygon;
        _t_min = rhs._t_min;
        _t_max = rhs._t_max;
        _control_point = rhs._control_point;

        if (rhs._vbo_control_polygon)
            UpdateVertexBufferObjectOfControlPolygon(_usage_flag_control_polygon);
    }
    return *this;
}

GLvoid ApproximatingSpline::SetDomain(GLdouble t_min, GLdouble t_max)
{
    _t_min = t_min;
    _t_max = t_max;
}

GLvoid ApproximatingSpline::GetDomain(GLdouble& t_min, GLdouble& t_max) const
{
    t_min = _t_min;
    t_max = _t_max;
}

GLboolean ApproximatingSpline::SetControlPoint(GLuint index, GLdouble x, GLdouble y, GLdouble z)
{
    if (index >= _control_point.GetRowCount())
        return false;

    DCoordinate& ref = _control_point.operator ()(index);
    ref.x = x;
    ref.y = y;
    ref.z = z;

    return true;
}

GLboolean ApproximatingSpline::SetControlPoint(GLuint index, const DCoordinate& p)
{
    if (index >= _control_point.GetRowCount())
        return false;

    _control_point(index) = p;

    return true;
}

GLboolean ApproximatingSpline::GetControlPoint(GLuint index, DCoordinate& p) const
{
    if (index >= _control_point.GetRowCount())
        return false;

    p = _control_point(index);

    return true;
}

GLboolean ApproximatingSpline::GenerateShape(GLuint division_point_count, GenericCurve& shape) const
{
    if (division_point_count < 2)
        return false;

    SplineInformation data;
    CalculateAllInformation(_t_min, data);
    GLuint derivative_count = data.GetRowCount();
    if (!derivative_count)
        return false;

    shape._derivative = Matrix<DCoordinate>(derivative_count, division_point_count);

    if (!shape._derivative.SetColumn(0, data))
        return false;

    GLuint n = division_point_count - 1;
    GLdouble dt = (_t_max - _t_min) / n;
    GLdouble t = _t_min;
    for (GLuint i = 1; i < n; ++i)
    {
        t += dt;
        CalculateAllInformation(t, data);
        if (!shape._derivative.SetColumn(i, data))
            return false;
    }

    CalculateAllInformation(_t_max, data);
    if (!shape._derivative.SetColumn(n, data))
        return false;

    return true;
}

GLboolean ApproximatingSpline::GenerateArc(GLdouble t_min, GLdouble t_max, GLuint division_point_count, GenericCurve& arc) const
{
    if (t_min > t_max || t_min < _t_min || t_max > _t_max || division_point_count < 2)
        return false;

    SplineInformation data;
    CalculateAllInformation(t_min, data);
    GLuint derivative_count = data.GetRowCount();
    if (!derivative_count)
        return false;

    arc._derivative = Matrix<DCoordinate>(derivative_count, division_point_count);

    if (!arc._derivative.SetColumn(0, data))
        return false;

    GLuint n = division_point_count - 1;
    GLdouble dt = (t_max - t_min) / n;
    GLdouble t = t_min;
    for (GLuint i = 1; i < n; ++i)
    {
        t += dt;
        CalculateAllInformation(t, data);
        if (!arc._derivative.SetColumn(i, data))
            return false;
    }

    CalculateAllInformation(t_max, data);
    if (!arc._derivative.SetColumn(n, data))
        return false;

    return true;
}

GLboolean ApproximatingSpline::UpdateControlPointsForInterpolation(const ColumnMatrix<GLdouble>& knot_vector, const ColumnMatrix<DCoordinate>& data_points)
{
    GLuint control_point_count = _control_point.GetRowCount();

    if (control_point_count != knot_vector.GetRowCount() || control_point_count != data_points.GetRowCount())
        return false;

    RealSquareMatrix collocation_matrix(control_point_count);

    RowMatrix<GLdouble> current_blending_values(control_point_count);
    for (GLuint r = 0; r < knot_vector.GetRowCount(); ++r)
    {
        if (!BlendingFunctionValues(knot_vector(r), current_blending_values))
            return false;
        else
            collocation_matrix.SetRow(r, current_blending_values);
    }

    return collocation_matrix.SolveLinearSystem(data_points, _control_point);
}

GLvoid ApproximatingSpline::DeleteVertexBufferObjectOfControlPolygon()
{
    if (_vbo_control_polygon)
    {
        glDeleteBuffersARB(1, &_vbo_control_polygon);
        _vbo_control_polygon = 0;
    }
}

GLboolean ApproximatingSpline::RenderControlPolygon(GLenum render_mode) const
{
    if (!_vbo_control_polygon)
        return false;

    if (render_mode != GL_LINE_STRIP && render_mode != GL_LINE_LOOP && render_mode != GL_POINTS)
        return false;

    if (_is_closed && render_mode == GL_LINE_STRIP)
        render_mode = GL_LINE_LOOP;

    glEnableClientState(GL_VERTEX_ARRAY);

        glBindBufferARB(GL_ARRAY_BUFFER, _vbo_control_polygon);
        glVertexPointer(3, GL_FLOAT, 0, (const GLvoid*)0);
        glDrawArrays(render_mode, 0, (GLint)_control_point.GetRowCount());

    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return true;
}

GLboolean ApproximatingSpline::UpdateVertexBufferObjectOfControlPolygon(GLenum usage_flag)
{
    GLuint control_point_count = _control_point.GetRowCount();

    if (!control_point_count)
        return false;

    if (usage_flag != GL_STREAM_DRAW  && usage_flag != GL_STREAM_READ  && usage_flag != GL_STREAM_COPY &&
        usage_flag != GL_STATIC_DRAW  && usage_flag != GL_STATIC_READ  && usage_flag != GL_STATIC_COPY &&
        usage_flag != GL_DYNAMIC_DRAW && usage_flag != GL_DYNAMIC_READ && usage_flag != GL_DYNAMIC_COPY)
        return false;

    _usage_flag_control_polygon = usage_flag;

    DeleteVertexBufferObjectOfControlPolygon();

    glGenBuffersARB(1, &_vbo_control_polygon);
    if (!_vbo_control_polygon)
        return false;

    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_control_polygon);
    glBufferDataARB(GL_ARRAY_BUFFER, control_point_count * 3 * sizeof(GLfloat), 0, _usage_flag_control_polygon);

    GLfloat* coordinate = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    for (GLuint p = 0; p < control_point_count; ++p)
    {
        for (GLuint c = 0; c < 3; ++c)
        {
            *coordinate = (GLfloat)_control_point(p)[c];
            ++coordinate;
        }
    }
    if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        return false;

    return true;
}

ApproximatingSpline::~ApproximatingSpline()
{
    DeleteVertexBufferObjectOfControlPolygon();
}
